const extend = (a, b) => {
  a = a || {};
  for (let n in b) a[n] = b[n];
  return a;
};
(($) => {
  let _modules = [];

  const loadModule = function () {
    let moduleName = null,
      path = null;
    if (arguments.length === 2) {
      moduleName = arguments[0];
      path = arguments[1];
    } else {
      path = arguments[0];
    }
    return new Promise((resolve, reject) => {
      try {
        const head = document.querySelector("head");
        const isJS = /\.js/.test(path) ? true : false;
        const script = document.createElement(isJS ? "script" : "link");
        head.appendChild(script);
        if (isJS) {
          script.src = path + "?t=" + new Date().getTime();
        } else {
          script.href = path;
          script.rel = "stylesheet";
          script.type = "text/css";
        }
        script.onload =
          script.onerror =
          script.onreadystatechange =
            function () {
              if (isJS) {
                if (moduleName) {
                  const res = _modules.filter(
                    (item) => item.name === moduleName
                  );
                  emi(res[0])
                    .then(() => res[0].cb(res[0].name, res[0]))
                    .then(() => resolve());
                } else {
                  resolve();
                }
              } else {
                resolve();
              }
            };
      } catch (e) {
        reject(e);
      }
    });
  };

  const extend = (a, b) => {
    a = a || {};
    for (let n in b) a[n] = b[n];
    return a;
  };

  const require = (name) => {
    let res = _modules.filter((item) => item.name === name);
    if (res[0] && res[0].status === status.LOADED)
      return (
        res[0].exports[name] ||
        (res[0].module.exports && res[0].module.exports[name]) ||
        res[0].exports ||
        res[0].module.exports
      );
    else {
      return null;
    }
  };

  require.async = (name, callback) => {
    return emi({ depend: name.split(" ") }).then(() => {
      typeof callback === "function" && callback();
    });
  };

  const emi = (ops) => {
    let all = [];
    ops.depend &&
      ops.depend.forEach((depend) => {
        const res = _modules.filter((item) => item.name === depend);
        res[0]
          ? all.push(loadModule(res[0].name, res[0].path))
          : all.push(loadModule(depend));
      });
    return Promise.all(all).catch((e) => console.error(e));
  };

  const define = function () {
    let moduleName = null,
      depend = [],
      callback = null;
    if (arguments.length === 3) {
      moduleName = arguments[0];
      depend = arguments[1];
      callback = arguments[2];
    } else if (arguments.length === 2) {
      moduleName = typeof arguments[0] === "array" ? null : arguments[0];
      depend = typeof arguments[0] === "string" ? depend : arguments[0];
      callback = arguments[1];
    } else if (arguments.length === 1) {
      callback = arguments[0];
    }
    callback.toString().replace(/require\(['"]+([^'"]+)['"]+\)/gim, (a, b) => {
      const res = _modules.filter((item) => item.name === b);
      depend.filter((item) => item === b).length === 0 &&
        depend.push(res[0].name);
      return a;
    });

    const filterRes = moduleName
      ? _modules.filter((item) => item.name === moduleName)
      : [];

    if (filterRes.length === 1) {
      let ops = {
        name: moduleName,
        depend,
        fn: callback,
      };
      extend(filterRes[0], ops);
    } else {
      let exports = {},
        module = { exports };

      emi({ depend }).then(() => callback(require, exports, module));
    }
  };

  const Module = (moduleName, path) => {
    let exports = {};
    const cb = (name, ops) => {
      if (ops.fn) {
        const res = ops.fn(ops.require, ops.exports, ops.module);
        res && extend(ops.exports, { [name]: res });
      }
      extend(ops, {
        exports: {
          ...ops.exports,
        },
        module: {
          ...ops.module.exports,
          ...ops.exports,
        },
        status: status.LOADED,
      });
    };
    return {
      name: moduleName,
      depend: null,
      fn: null,
      cb,
      path: path,
      status: status.NOLOAD,
      require,
      exports,
      module: { exports },
    };
  };

  $.define = define;
  $.define.amd = true;

  const status = {
    NOLOAD: "noload",
    LOADED: "loaded",
  };
  class Require {
    constructor() {
      return this;
    }
    config(ops) {
      if (ops && ops.alias) {
        let modules = [];
        for (let n in ops.alias) {
          modules.push(Module(n, ops.alias[n]));
        }
        _modules = [..._modules, ...modules];
      }
      return this;
    }
    use(paths) {
      if (
        typeof paths === "array" ||
        (typeof paths === "string" && paths.split(" ").length > 1)
      ) {
        let all = [];
        paths.forEach((path) => {
          const res = _modules.filter((item) => item.name === path);
          res.length > 0
            ? all.push(loadModule(res[0].name, res[0].path))
            : all.push(loadModule(path));
        });
        return Promise.all(all).catch((e) => console.error(e));
      }
      return loadModule(paths).catch((e) => console.error(e));
    }
  }
  $.Require = Require;
})(this);
